package dao

import (
	"encoding/json"
	"fmt"
	"gbox-one-machine/models"

	"context"
	"net/http"
)

// http path
const (
	devicesPath            = "/api/v1/resource_center/devices"
	subDevicesPath         = "/api/v1/resource_center/devices?wait=true&timeout=15&id=%s"
	deviceCameraPath       = "/api/v1/resource_center/device/camera"
	deviceNVRPath          = "/api/v1/resource_center/device/nvr"
	deviceRefreshPath      = devicesPath + "/refresh"
	deviceGetCameraPath    = deviceCameraPath + "/%s"
	deviceGetNVRPath       = deviceNVRPath + "/%s"
	deviceSetCameraPath    = deviceCameraPath + "/%s"
	deviceUpdateCameraPath = deviceCameraPath + "/%s"
	deviceUpdateNVRPath    = deviceNVRPath + "/%s"
	deviceSetNVRPath       = deviceNVRPath + "/%s"
	deviceCleanCameraPath  = deviceCameraPath + "/clean"
	deviceCleanNVRPath     = deviceNVRPath + "/clean"
)

type devicesEventResp struct {
	ReturnCode   int                  `json:"returnCode"`
	ErrorMessage string               `json:"errorMessage"`
	Result       *models.DevicesEvent `json:"result"`
}

// WatchDevices watch devices
func (d *Dao) WatchDevices(ctx context.Context, id string, cb func(devices *models.DevicesEvent) error) error {
	if id == "" {
		return fmt.Errorf("ID is empty")
	}

	url := d.resCenterHost + fmt.Sprintf(subDevicesPath, id)

	if err := d.SubLongPoll(ctx, url, func(data []byte) error {

		resResp := new(devicesEventResp)
		if err := json.Unmarshal(data, resResp); err != nil {
			return fmt.Errorf("Resp decode err: %s body: %s", err, data)
		}

		if err := cb(resResp.Result); err != nil {
			return err
		}
		return nil
	}); err != nil {
		return err
	}

	return nil
}

type devicesResp struct {
	ReturnCode   int             `json:"returnCode"`
	ErrorMessage string          `json:"errorMessage"`
	Result       *models.Devices `json:"result"`
}

// GetDevices get devices info
func (d *Dao) GetDevices(ctx context.Context) (*models.Devices, error) {

	resResp := new(devicesResp)

	resp, err := d.http().R().SetContext(ctx).Get(d.resCenterHost + devicesPath)
	if err != nil {
		return nil, fmt.Errorf("HTTP request err: %s", err)
	}

	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("HTTP status %s", resp.Status())
	}

	if err := json.Unmarshal(resp.Body(), resResp); err != nil {
		return nil, err
	}

	if resResp.ReturnCode != 0 {
		return nil, fmt.Errorf("Errcode: %d, Msg: %s", resResp.ReturnCode, resResp.ErrorMessage)
	}

	return resResp.Result, nil
}

type refreshDevicesResp struct {
	ReturnCode   int      `json:"returnCode"`
	ErrorMessage string   `json:"errorMessage"`
	Result       struct{} `json:"result"`
}

// RefreshDevices notice devices changed
func (d *Dao) RefreshDevices(ctx context.Context) error {
	resResp := new(refreshDevicesResp)

	resp, err := d.http().R().SetContext(ctx).Post(d.resCenterHost + deviceRefreshPath)
	if err != nil {
		return fmt.Errorf("HTTP request err: %s", err)
	}

	if resp.StatusCode() != http.StatusOK {
		return fmt.Errorf("HTTP status %s", resp.Status())
	}

	if err := json.Unmarshal(resp.Body(), resResp); err != nil {
		return err
	}

	if resResp.ReturnCode != 0 {
		return fmt.Errorf("Errcode: %d, Msg: %s", resResp.ReturnCode, resResp.ErrorMessage)
	}

	return nil
}

type cameraResp struct {
	ReturnCode   int            `json:"returnCode"`
	ErrorMessage string         `json:"errorMessage"`
	Result       *models.Camera `json:"result"`
}

// GetCameraDevice get camera device
func (d *Dao) GetCameraDevice(ctx context.Context, id string) (*models.Camera, error) {
	if id == "" {
		return nil, fmt.Errorf("ID empty")
	}
	resResp := new(cameraResp)

	resp, err := d.http().R().SetContext(ctx).Get(fmt.Sprintf(d.resCenterHost+deviceGetCameraPath, id))
	if err != nil {
		return nil, fmt.Errorf("HTTP request err: %s", err)
	}

	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("HTTP status %s", resp.Status())
	}

	if err := json.Unmarshal(resp.Body(), resResp); err != nil {
		return nil, err
	}

	if resResp.ReturnCode != 0 {
		return nil, fmt.Errorf("Errcode: %d, Msg: %s", resResp.ReturnCode, resResp.ErrorMessage)
	}

	return resResp.Result, nil
}

// GetCamerasMap get all cameras map
func (d *Dao) GetCamerasMap(ctx context.Context) (map[string]*models.Camera, error) {

	cameraMap := make(map[string]*models.Camera)

	cameras, err := d.GetCameras(ctx)
	if err != nil {
		return nil, err
	}

	for _, camera := range cameras {
		cameraMap[camera.ID] = camera
	}

	return cameraMap, nil
}

type camerasResp struct {
	ReturnCode   int              `json:"returnCode"`
	ErrorMessage string           `json:"errorMessage"`
	Result       []*models.Camera `json:"result"`
}

// GetCameras get all cameras
func (d *Dao) GetCameras(ctx context.Context) ([]*models.Camera, error) {

	resResp := new(camerasResp)

	resp, err := d.http().R().SetContext(ctx).Get(d.resCenterHost + deviceCameraPath)
	if err != nil {
		return nil, fmt.Errorf("HTTP request err: %s", err)
	}

	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("HTTP status %s", resp.Status())
	}

	if err := json.Unmarshal(resp.Body(), resResp); err != nil {
		return nil, err
	}

	if resResp.ReturnCode != 0 {
		return nil, fmt.Errorf("Errcode: %d, Msg: %s", resResp.ReturnCode, resResp.ErrorMessage)
	}

	return resResp.Result, nil
}

type setCameraResp struct {
	ReturnCode   int         `json:"returnCode"`
	ErrorMessage string      `json:"errorMessage"`
	Result       interface{} `json:"result"`
}

// SetCameraDevice set Camera device
func (d *Dao) SetCameraDevice(ctx context.Context, camera *models.Camera) error {

	resResp := new(setCameraResp)

	resp, err := d.http().R().SetContext(ctx).SetBody(camera).Put(fmt.Sprintf(d.resCenterHost+deviceSetCameraPath, camera.ID))
	if err != nil {
		return fmt.Errorf("HTTP request err: %s", err)
	}

	if resp.StatusCode() != http.StatusOK {
		return fmt.Errorf("HTTP status %s", resp.Status())
	}

	if err := json.Unmarshal(resp.Body(), resResp); err != nil {
		return err
	}

	if resResp.ReturnCode != 0 {
		return fmt.Errorf("Errcode: %d, Msg: %s", resResp.ReturnCode, resResp.ErrorMessage)
	}

	return nil
}

type updateCameraResp struct {
	ReturnCode   int         `json:"returnCode"`
	ErrorMessage string      `json:"errorMessage"`
	Result       interface{} `json:"result"`
}

// UpdateCamera set Camera device
func (d *Dao) UpdateCamera(ctx context.Context, id string, updateData map[string]interface{}) error {

	resResp := new(updateCameraResp)

	resp, err := d.http().R().SetContext(ctx).SetBody(updateData).Patch(fmt.Sprintf(d.resCenterHost+deviceUpdateCameraPath, id))
	if err != nil {
		return fmt.Errorf("HTTP request err: %s", err)
	}

	if resp.StatusCode() != http.StatusOK {
		return fmt.Errorf("HTTP status %s", resp.Status())
	}

	if err := json.Unmarshal(resp.Body(), resResp); err != nil {
		return err
	}

	if resResp.ReturnCode != 0 {
		return fmt.Errorf("Errcode: %d, Msg: %s", resResp.ReturnCode, resResp.ErrorMessage)
	}

	return nil
}

type cleanCameraResp struct {
	ReturnCode   int         `json:"returnCode"`
	ErrorMessage string      `json:"errorMessage"`
	Result       interface{} `json:"result"`
}

// CleanCameras clean cameras
func (d *Dao) CleanCameras(ctx context.Context) error {

	resResp := new(cleanCameraResp)

	resp, err := d.http().R().SetContext(ctx).Post(d.resCenterHost + deviceCleanCameraPath)
	if err != nil {
		return fmt.Errorf("HTTP request err: %s", err)
	}

	if resp.StatusCode() != http.StatusOK {
		return fmt.Errorf("HTTP status %s", resp.Status())
	}

	if err := json.Unmarshal(resp.Body(), resResp); err != nil {
		return err
	}

	if resResp.ReturnCode != 0 {
		return fmt.Errorf("Errcode: %d, Msg: %s", resResp.ReturnCode, resResp.ErrorMessage)
	}

	return nil
}

type nvrResp struct {
	ReturnCode   int         `json:"returnCode"`
	ErrorMessage string      `json:"errorMessage"`
	Result       *models.NVR `json:"result"`
}

// GetNVRDevice get nvr device
func (d *Dao) GetNVRDevice(ctx context.Context, id string) (*models.NVR, error) {
	if id == "" {
		return nil, fmt.Errorf("ID empty")
	}
	resResp := new(nvrResp)

	resp, err := d.http().R().SetContext(ctx).Get(fmt.Sprintf(d.resCenterHost+deviceGetNVRPath, id))
	if err != nil {
		return nil, fmt.Errorf("HTTP request err: %s", err)
	}

	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("HTTP status %s", resp.Status())
	}

	if err := json.Unmarshal(resp.Body(), resResp); err != nil {
		return nil, err
	}

	if resResp.ReturnCode != 0 {
		return nil, fmt.Errorf("Errcode: %d, Msg: %s", resResp.ReturnCode, resResp.ErrorMessage)
	}

	return resResp.Result, nil
}

// GetNVRsMap get all nvrs map
func (d *Dao) GetNVRsMap(ctx context.Context) (map[string]*models.NVR, error) {

	nvrMap := make(map[string]*models.NVR)

	nvrs, err := d.GetNVRs(ctx)
	if err != nil {
		return nil, err
	}

	for _, nvr := range nvrs {
		nvrMap[nvr.ID] = nvr
	}

	return nvrMap, nil
}

type updateNVRResp struct {
	ReturnCode   int         `json:"returnCode"`
	ErrorMessage string      `json:"errorMessage"`
	Result       interface{} `json:"result"`
}

// UpdateNVR set NVR device
func (d *Dao) UpdateNVR(ctx context.Context, id string, updateData map[string]interface{}) error {

	resResp := new(updateNVRResp)

	resp, err := d.http().R().SetContext(ctx).SetBody(updateData).Patch(fmt.Sprintf(d.resCenterHost+deviceUpdateNVRPath, id))
	if err != nil {
		return fmt.Errorf("HTTP request err: %s", err)
	}

	if resp.StatusCode() != http.StatusOK {
		return fmt.Errorf("HTTP status %s", resp.Status())
	}

	if err := json.Unmarshal(resp.Body(), resResp); err != nil {
		return err
	}

	if resResp.ReturnCode != 0 {
		return fmt.Errorf("Errcode: %d, Msg: %s", resResp.ReturnCode, resResp.ErrorMessage)
	}

	return nil
}

type nvrsResp struct {
	ReturnCode   int           `json:"returnCode"`
	ErrorMessage string        `json:"errorMessage"`
	Result       []*models.NVR `json:"result"`
}

// GetNVRs get all nvrs
func (d *Dao) GetNVRs(ctx context.Context) ([]*models.NVR, error) {

	resResp := new(nvrsResp)

	resp, err := d.http().R().SetContext(ctx).Get(d.resCenterHost + deviceNVRPath)
	if err != nil {
		return nil, fmt.Errorf("HTTP request err: %s", err)
	}

	if resp.StatusCode() != http.StatusOK {
		return nil, fmt.Errorf("HTTP status %s", resp.Status())
	}

	if err := json.Unmarshal(resp.Body(), resResp); err != nil {
		return nil, err
	}

	if resResp.ReturnCode != 0 {
		return nil, fmt.Errorf("Errcode: %d, Msg: %s", resResp.ReturnCode, resResp.ErrorMessage)
	}

	return resResp.Result, nil
}

type setNVRResp struct {
	ReturnCode   int         `json:"returnCode"`
	ErrorMessage string      `json:"errorMessage"`
	Result       interface{} `json:"result"`
}

// SetNVRDevice set nvr device
func (d *Dao) SetNVRDevice(ctx context.Context, nvr *models.NVR) error {

	resResp := new(setNVRResp)

	resp, err := d.http().R().SetContext(ctx).SetBody(nvr).Put(fmt.Sprintf(d.resCenterHost+deviceSetNVRPath, nvr.ID))
	if err != nil {
		return fmt.Errorf("HTTP request err: %s", err)
	}

	if resp.StatusCode() != http.StatusOK {
		return fmt.Errorf("HTTP status %s", resp.Status())
	}

	if err := json.Unmarshal(resp.Body(), resResp); err != nil {
		return err
	}

	if resResp.ReturnCode != 0 {
		return fmt.Errorf("Errcode: %d, Msg: %s", resResp.ReturnCode, resResp.ErrorMessage)
	}

	return nil
}

type cleanNVRResp struct {
	ReturnCode   int         `json:"returnCode"`
	ErrorMessage string      `json:"errorMessage"`
	Result       interface{} `json:"result"`
}

// CleanNVRs clean nvrs
func (d *Dao) CleanNVRs(ctx context.Context) error {

	resResp := new(cleanNVRResp)

	resp, err := d.http().R().SetContext(ctx).Post(d.resCenterHost + deviceCleanNVRPath)
	if err != nil {
		return fmt.Errorf("HTTP request err: %s", err)
	}

	if resp.StatusCode() != http.StatusOK {
		return fmt.Errorf("HTTP status %s", resp.Status())
	}

	if err := json.Unmarshal(resp.Body(), resResp); err != nil {
		return err
	}

	if resResp.ReturnCode != 0 {
		return fmt.Errorf("Errcode: %d, Msg: %s", resResp.ReturnCode, resResp.ErrorMessage)
	}

	return nil
}
